-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
fix: support buffers greater than 2^32 bytes in length in Buffer.concat and Buffer.copy
#55492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
03d27e2 to
d15c192
Compare
|
Hi! Can you please amend your commit message to follow the guidelines of this repository? It needs to be under 80 characters |
|
Additionally, can you add a test to validate this behavior |
d15c192 to
cd0a59a
Compare
|
I noticed that |
No need, it's a flaky test. It's a known process deadlock that's being investigated. |
|
@ronag I've requested your review because you were involved in the issue this closes, and you know a lot about buffers that I don't |
|
Don't merge this yet. It's possible to read bytes from outside the source buffer into the target buffer. For example... function _copyActual(source, target, targetStart, sourceStart, sourceEnd) {
if (sourceEnd - sourceStart > target.byteLength - targetStart)
sourceEnd = sourceStart + target.byteLength - targetStart;
let nb = sourceEnd - sourceStart;
const sourceLen = source.byteLength - sourceStart;
if (nb > sourceLen)
nb = sourceLen;
if (nb <= 0)
return 0;
// _copy(source, target, targetStart, sourceStart, nb);
// return nb;
return [source, target, targetStart, sourceStart, nb];
}
let src = Buffer.alloc(50);
let dest = Buffer.alloc(100);
const [_, __, ___, sourceStart, copy_len] = _copyActual(src, dest, 0, -10, 0);
console.log([sourceStart, copy_len]); // [ -10, 10 ]Its probably better here not to trust the caller of Resolved by: 3c18a13 |
|
@nodejs/buffer PTAL ☝️ |
|
I've updated The expected behavior for an out of bounds copy is actually specified by At the latest commit 3c18a13, |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #55492 +/- ##
========================================
Coverage 88.41% 88.41%
========================================
Files 653 654 +1
Lines 187435 187575 +140
Branches 36077 36081 +4
========================================
+ Hits 165714 165851 +137
+ Misses 14960 14954 -6
- Partials 6761 6770 +9
🚀 New features to boost your workflow:
|
|
Not sure what do about code coverage issue. It seems to me that the range check should stay in as preventative measure. |
|
No need to worry about codecov, it's not 100% accurate |
lib/buffer.js
Outdated
| return 0; | ||
|
|
||
| // Guaranteed 0 <= sourceStart < sourceEnd (NO GUARANTEE <= source.byteLnegth) | ||
| // Guaranteed 0 <= targetStart < target.byteLength here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could just make these asserts. The perf impact is negligable...
lib/buffer.js
Outdated
|
|
||
| // Assumes `source`, `target` are real buffers. | ||
| // Assumes 0 <= sourceStart <= sourceEnd | ||
| // Assumes 0 <= targetStart <= target.byteLength |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could just make these asserts. The perf impact is negligable...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 271 to 275 in 209289c
| assert(isUint8Array(source) && isUint8Array(target)); | |
| // Enforce: 0 <= sourceStart <= sourceEnd <= source.byteLength | |
| assert(0 <= sourceStart && sourceStart <= sourceEnd && sourceEnd <= source.byteLength); | |
| // Enforce: 0 <= targetStart<= target.byteLength | |
| assert(0 <= targetStart && targetStart <= target.byteLength); |
|
Turns out the issue also occurs in |
Buffer.concat and Buffer.copy
Buffer.concat and Buffer.copyBuffer.concat and Buffer.copy
Buffer.concat and Buffer.copy2^32 bytes in length in Buffer.concat and Buffer.copy
lib/buffer.js
Outdated
| if (targetStart < 0) | ||
| throw new ERR_OUT_OF_RANGE('targetStart', '>= 0', targetStart); | ||
| } | ||
| // Guaranteed 0 <= targetStart |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean?
Shouldn't an assert be used instead?
If not, IMO a comment isn't needed at all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it is redundant. This comment has been removed as of commit 5ca703f.
test/parallel/test-buffer-concat.js
Outdated
| // Test its possible to concat buffers where the total length of the result in bytes | ||
| // is greater than 2^32. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| // Test its possible to concat buffers where the total length of the result in bytes | |
| // is greater than 2^32. | |
| // Ref: ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/parallel/test-buffer-copy.js
Outdated
| } | ||
|
|
||
|
|
||
| // Test its possible to copy buffer with length > 2^32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
21b9ce0 to
5ca703f
Compare
|
@redyetidev This is no longer WIP unless you have anymore concerns or suggestions. |
|
I believe CI is failing because the runner is exhausting its memory. We do some pretty large allocations in the unit tests. node/test/parallel/test-buffer-copy.js Lines 239 to 245 in 5ca703f
node/test/parallel/test-buffer-concat.js Lines 103 to 113 in 5ca703f
Perhaps the tests should be skipped? |
| // - The source slice is not clamped to fit into the target slice. If it won't fit, this throws. | ||
| // - If either the source or target slice are out of bounds, this throws. | ||
| function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { | ||
| assert(isUint8Array(source) && isUint8Array(target)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assert here is a bit redundant given the checks that are made in the copyImpl call site.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion this assert is useful since it makes _copyActual's invariant that source and target need to be Uint8Array explicit.
It indicates to any future/new maintainers needing to call _copyActual what the invariants are. They don't have to guess at the invariants, or deduce the preconditions by exploring all the call-sites of _copyActual.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| new Uint8Array([0x43, 0x44])]), | ||
| Buffer.from('ABCD')); | ||
|
|
||
| // Ref: https://github.com/nodejs/node/issues/55422#issue-2594486812 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: please consider expanding the comment a bit here with a short summary of what is being tested for so folks don't have to necessarily follow the link to get the gist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it is better to have the description of the test inlined.
However I was under the impression that the policy was to use refs?
See #55492 (comment) where I actually removed the explanation at the request of another maintainer.
|
What's the status of this? |
| // Enforce: 0 <= sourceStart <= sourceEnd <= source.byteLength | ||
| assert(0 <= sourceStart && sourceStart <= sourceEnd && sourceEnd <= source.byteLength); | ||
| // Enforce: 0 <= targetStart<= target.byteLength | ||
| assert(0 <= targetStart && targetStart <= target.byteLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also likely fix #59985
| const targetCapacity = target.byteLength - targetStart; | ||
| assert(copyLength <= targetCapacity); | ||
|
|
||
| _copy(source, target, targetStart, sourceStart, copyLength); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_copy is slower than set but #60399 would fix that
| // - The source slice is not clamped to fit into the target slice. If it won't fit, this throws. | ||
| // - If either the source or target slice are out of bounds, this throws. | ||
| function _copyActual(source, target, targetStart, sourceStart, sourceEnd) { | ||
| assert(isUint8Array(source) && isUint8Array(target)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closes #55422
This pull request...
Buffer.copywith lengths greater than or equal to 2^32 bytes.Buffer.concatwhere the result buffer has length greater than or equal to 2^32 bytes.Buffer.copy.sourceEndexceeds the total number of bytes in the source buffer.Buffer.copyandBuffer.concat.